package sudoku.support

import sudoku.SudokuSolverProperties
import sudoku.draft.Cache
import sudoku.draft.Pos
import java.text.SimpleDateFormat
import java.util.*
import kotlin.collections.ArrayList

/**
 * @author flutterdash@qq.com
 * @since 2021/10/1 22:20
 */
object DashBoard {
    var status: SudokuStatus = SudokuStatus.SOLVING
    val time: Long = System.currentTimeMillis()
    val selects: ArrayList<Pos> = ArrayList()
}

fun doSolve(board: Board, properties: SudokuSolverProperties) {
    VisualBoard.init(board)
    Cache.init(board)
    Screen.setRawBoard(board)

    initSolver(properties)

    solveCore(board)
}

fun initSolver(properties: SudokuSolverProperties) {
    LogService.config(properties.showLogCount)
    Screen.config(properties)
}

fun solveCore(board: Board, pos: Pos = Pos()): SudokuStatus {
    if (pos.finished) return doFinished()

    if (board[pos.row][pos.col] == 0) { // selectable
        val candidates = selectCandidates(pos)
        for (candi in candidates) {
            selectedPosition(pos, candi)

            drawAndRefresh(pos)
            undo()

            if (solveCore(board, pos.next()) == SudokuStatus.FINISHED)
                return SudokuStatus.FINISHED
            unselectedPosition(pos, candi)
        }
    } else { // select next position
        return solveCore(board, pos.next())
    }

    return doFailure()
}

fun drawAndRefresh(pos: Pos) {
    for (c in 0 until side) {
        DashBoard.selects.add(Pos(pos.row, c))
        DashBoard.selects.add(Pos(c, pos.col))
    }
    DashBoard.selects.addAll(getBoxOf(pos))
    for (p in DashBoard.selects) {
        VisualBoard[p.row, p.col].pushStyle {
            style.onSearch()
        }
    }
    DashBoard.selects.add(pos)
    VisualBoard[pos.row, pos.col].pushStyle {
        style.onHit()
    }
    Screen.refresh()
}

fun undo() {
    DashBoard.selects.forEach { pos ->
        VisualBoard[pos.row, pos.col].removeStyle()
    }
    DashBoard.selects.clear()
}

fun selectedPosition(pos: Pos, num: Int) {
    Cache.makeMark(pos.row, pos.col, num)
    LogService.genLog(pos, "尝试数字`$num`")
    VisualBoard[pos.row, pos.col].pushStyle {
        text = num.toString()
    }
}

fun unselectedPosition(pos: Pos, num: Int) {
    Cache.removeMark(pos.row, pos.col, num)
    LogService.genLog(pos, "撤销数字`$num`")
    VisualBoard[pos.row, pos.col].removeStyle()
}

fun selectCandidates(pos: Pos): List<Int> {
    val candidates = ArrayList<Int>()
    for (num in 1..9) {
        if (Cache.canUse(pos.row, pos.col, num)) {
            candidates.add(num)
        }
    }

    LogService.genLog(pos, "候选项:$candidates")
    return candidates
}

fun doFinished(): SudokuStatus {
    LogService.genLog("FINISHED!! :)")
    DashBoard.status = SudokuStatus.FINISHED
    return SudokuStatus.FINISHED
}

fun doFailure(): SudokuStatus {
    LogService.genLog("FAILURE :(")
    DashBoard.status = SudokuStatus.FAILURE
    return SudokuStatus.FAILURE
}

// struct

enum class SudokuStatus {
    SOLVING,
    FINISHED,
    FAILURE
}

object LogService {
    private var count: Int = 1
    private val logQue: Deque<String> = LinkedList()
    private var id = 0

    fun config(count: Int) {
        this.count = count
    }

    fun genLog(pos: Pos, log: String) {
        genLog("${showPos(pos)}$log")
    }

    fun genLog(log: String) {
        if (logQue.size == count)
            logQue.removeFirst()
        logQue.addLast("id:${idIncreases()}> $log")
    }

    private fun idIncreases(): String {
        id++
        val str = id.toString()
        val rest = 5 - str.length
        return "0".repeat(rest) + str
    }

    fun getLastLogs(): List<String> {
        return ArrayList<String>().apply {
            for (log in logQue)
                this@apply.add(log)
        }
    }

    private fun showPos(pos: Pos) = "位置[${pos.row}, ${pos.col}], "
}

object Screen {
    private val buff = StringBuilder()
    private lateinit var rawBoardLines: List<String>
    private lateinit var properties: SudokuSolverProperties
    private val dateFormatter = SimpleDateFormat("mm:ss")

    fun setRawBoard(board: Board) {
        this.rawBoardLines = buildTextBoard(board).lines().map { line ->
            "$normalStyle$line${" ".repeat(10)}"
        }
    }

    fun config(properties: SudokuSolverProperties) {
        this.properties = properties
    }

    fun refresh() {
        buff.setLength(0)
        printTitle()
        printBoard()
        printLogs()

        clear()
        println(buff)
        sleep()
    }

    private fun printTitle() {
        val current = Date(System.currentTimeMillis() - DashBoard.time)
        buff.append("visual-sudoku-solver@${properties.version} by https://gitee.com/applesack   *2021/10/2*\n")
        buff.append("speed ${properties.speed}x  ${dateFormatter.format(current)}\n\n")
    }

    private fun printBoard() {
        printBoardHeader()
        weaveBoard()
    }

    private fun printBoardHeader() {
        buff.append("  [raw]                           status: [${DashBoard.status}] \n")
    }

    private fun weaveBoard() {
        val solvingLines = VisualBoard.showCurrentBoard().lines()
        val buff = StringBuilder()
        for (idx in rawBoardLines.indices) {
            if (idx == rawBoardLines.size - 1)
                break

            buff.append(rawBoardLines[idx])
            buff.append(solvingLines[idx]).append('\n')
        }
        this.buff.append(buff).append('\n')
    }

    private fun printLogs() {
        buff.append("日志: 显示最近${properties.showLogCount}条\n")
        LogService.getLastLogs().forEach { log ->
            buff.append(log).append('\n')
        }
        buff.append("... ...\n")
    }

    private fun sleep() {
        ConsoleHelper.sleep(properties.restInterval / properties.speed)
    }

    private fun clear() = ConsoleHelper.clear(buff.length)
}
